﻿package cerna_pairs;

/**统计ceRNA的N、K、n、c值的表，输入之前需要进行排序，否则会计算出错 （# 目前已经修正了这个bug，但是还是建议进行一个排序）
 * 已经建立了完善的超几何检验和fdr矫正的计算方法，保存在Phyer和FDR中，只包含计算方法，并没有新建类
 * 添加了一个Time方法，用来输出运行的总时间
 * 缺陷：
 * 由于没有掌握多线程编程的技巧，所以仅仅是单线程运算，即便如此经过测试，运算速度也比R快的多，毕竟R不擅长循环
 * 下一步：添加多线程计算方法
 * 		  可以输出两份文件一份all，一份只包含q < 0.05 的差异
 * 
 * 这是一个测试项目，用来测试我的多线程构想,
 * 2016.09.02	全自动生成多线程的方法已经找到，
 * 使用数组，吃饭回来改
 *	2016.09.06	昨天有了新思路今天开始作死重写
 *	在读取文件的使用其实用一个HashMap就可以统计所有的K、n值，问题在于如何统计c值
 *	以及改写所有的list，map和set类，我用了太多的类型转换
 *	2016.09.06
 *	修复重大bug
*/ 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.Map;
import java.util.HashMap;
import java.lang.Thread;
import java.lang.Runnable;

public class Main_method {
	public static void main (String args[]){
		
		if(args.length == 0 || args[0].equals("-h") || args[0] == "--help"){
			System.out.println("#########################################################");
			System.out.println("#" + "\t\t   " + "-h或--help参数输出帮助" + "\t\t\t" + "#");
			System.out.println("#" + "\t\t" + "该程序用来计算ceRNA对的p值与q值" + "\t\t" + "#");
			System.out.println("#" + "\t" + "第一个参数为miRNA与RNA的配对文件，第一列为miRNA" + "\t" + "#");
			System.out.println("#" + "\t\t       " + "第二个参数为输出文件" + "\t\t\t" + "#");
			System.out.println("#########################################################");
		}else{
			long start = System.currentTimeMillis();
			File cerna = new File(args[0]); // 使用第一个参数作为输入文件

			// 首先判断文件是否存在
			if(cerna.exists() && cerna.isFile()){
				System.out.println("开始读取文件:" + args[0]);
			}else{
				System.out.println(args[0] + "文件不存在");
				return;
			}

			// 初始化一个用来储存结果的results
			List<value_pairs> results = new ArrayList<value_pairs>();
			
			try {
				// 创建几个缓冲用来读取文件
				FileReader fr = new FileReader(cerna);

				BufferedReader br = new BufferedReader(fr);
				// 使用循环读取内容，一行一行的读，如果最后一个变成空，那么就停止
				String content = null;

				// 使用两个List将数据导入
				
				Set<String> mirnalist = new HashSet<String>();	// 用来储存所有的microrna，不具有重复的
				Set<String> rnalist = new HashSet<String>();	//	用来春存所有的rna，不具有重复的
				
				Map<String, Set<String>> forc = new HashMap<String, Set<String>>();	//	用一个Map，储存rna和其对应的所有的microrna
				
				while ((content = br.readLine()) != null){
					// System.out.println(content);

					String[] contentarray = content.split("\\s+");
										
					mirnalist.add(contentarray[0]);
					rnalist.add(contentarray[1]);
					
					// 一个临时的Set类，害怕直接整合到put命令中会出错
					Set<String> tem = new HashSet<String>();
					
					// 如果一个rna是第一次出现，那么赋值为1，以后没出现一次就+1，这么一来读取完成所有文件以后就统计好了所有的K和n的值
					if(forc.containsKey(contentarray[1])){
					
						tem = forc.get(contentarray[1]);
						tem.add(contentarray[0]);
						forc.put(contentarray[1], tem);
					}else{
						
						tem.add(contentarray[0]);
						forc.put(contentarray[1], tem);
					}
					
					

				}
				fr.close();
				br.close();
				System.out.println("文件读取已完成");

				
				// 为多线程做准备工作，默认开启三个线程
				int num = 0;
				if(args.length > 2 && args[2].equals("-p")){
					num = 1;
				}
				
				// 判断一下,如果num > 1 了那么就使用多线程， 其它情况下使用单线程			
				if(num == 1){
					System.out.println("开始多线程计算，使用" + 4 + "个线程");
					//////////////////////////////////			多线程			///////////////////////////////////////
					int rnalen = rnalist.size()/2;
					
					List rna1 = new ArrayList();

					List rna2 = new ArrayList();
					
					List rna3 = new ArrayList();
					List rna4 = new ArrayList();

					
					System.out.println("分割RNA列表中");
					// 用两个循环加判断，将rna数据拆开
					for(int i = 0; i < rnalist.size(); i++){										
						if(i < rnalen){
							rna1.add(rnalist.toArray()[i]);
						}else{
							rna2.add(rnalist.toArray()[i]);
						}
					}
					
					for(int i = 0; i < rna1.size(); i++){										
						if(i < rna1.size()/2){
							rna3.add(rna1.get(i));
						}else{
							rna4.add(rna1.get(i));
						}
					}
					
					
					
					System.out.println("RNA列表分割完成，准备分线程计算");
					/*
					if((rna1.size() + rna2.size() + rna3.size()) == rnalist.size()){
						System.out.println("提取的没有问题");
						return;
					}
					*/
					
					// 这里是使用Runable接口使用无返回值的多线程，future返回值存在问题，我们需要将这些值返回为计算的结果，也就是需要将其嵌入到merge的计算过程中，，懒的改了，，，
					Multi m1 = new Multi();
					m1.setMulti(rna1, forc, mirnalist.size(), args[1] + "tem1", "1");
					Thread t1 = new Thread(m1);
					
					Multi m2 = new Multi();
					m2.setMulti(rna2, forc, mirnalist.size(), args[1] + "tem2", "2");
					Thread t2 = new Thread(m2);
					
					Multi m3 = new Multi();
					m3.setMulti(rna2, rna3, forc, mirnalist.size(), args[1] + "tem3", "3");
					Thread t3 = new Thread(m3);
					
					Multi m4 = new Multi();
					m4.setMulti(rna2, rna4, forc, mirnalist.size(), args[1] + "tem4", "4");
					Thread t4 = new Thread(m4);
					
					try{
						t1.start();
						t2.start();
						t3.start();
						t4.start();
						
						Thread.sleep(4000L);
						
						t1.join();
						t2.join();
						t3.join();
						t4.join();
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					
					
					// 多线程的计算已经结束，清除一些不必要的变量，腾出内存，新建一个double类的list用来收集p值，然后计算q值
					
					List<Double> p = new ArrayList<Double>();
					
					results.clear();
					
					// 新建一个文件，用来整合所有的临时结果
					File merge = new File(args[1] + ".merge");
					
					if(merge.exists() && merge.isFile()){
						System.out.println("使用已经存在的文件" + merge);
					}else{
						try{
							merge.createNewFile();
							System.out.println("创建新文件");
						}catch (IOException e){
							System.out.println("创建文件失败，错误信息：" + e.getMessage());
							return;
						}
					}
					FileWriter fwmerge = new FileWriter(merge);
					BufferedWriter bwmerge = new BufferedWriter(fwmerge);
										
					for(int i = 1; i <= 4; i++){
						File hebing = new File(args[1]+ "tem" + Integer.toString(i));
						
						// 判断我们预输出的文件是否存在
						if(hebing.exists() && hebing.isFile()){
							System.out.println("开始读取文件:" + args[1]+ "tem" + Integer.toString(i));
							
							FileReader fh = new FileReader(hebing);
							BufferedReader bh = new BufferedReader(fh);
							
							while ((content = bh.readLine()) != null){
								
								// 将所有的临时文件整合起来输出
								bwmerge.write(content);
								bwmerge.newLine();
								
								// 因为这里内存极有可能不够，所以只输入p值，用于计算q值，随后重新按顺序读取并且整合输出							
								String[] contentarray = content.split("\\s+");
								p.add(Double.valueOf(contentarray[6]));
													
							}
							bh.close();
							fh.close();
							// 因为临时文件已经被整合了，所以没用的临时文件就删除
							hebing.delete();
						}else{
							System.out.println(args[1]+ "tem" + Integer.toString(i) + "文件不存在");
							return;
						}										
					}
					bwmerge.close();
					fwmerge.close();
					
					/**
					//	p值已经统计完成
					System.out.println("开始统计q值");
					FDR fdr = new FDR();
					double[] q = fdr.padjust(p);
					*/
					// 输出最终结果
					File output = new File(args[1]);

					if(output.exists() && output.isFile()){
						System.out.println("使用已经存在的文件" + output);
					}else{
						try{
							output.createNewFile();
							System.out.println("创建新文件");
						}catch (IOException e){
							System.out.println("创建文件失败，错误信息：" + e.getMessage());
							return;
						}
					}
					
					
					// 创建BufferedWriter
					FileWriter fw = new FileWriter(output.getAbsoluteFile());
					BufferedWriter bw = new BufferedWriter(fw);
					// 写入表头，换行
					// bw.write(new value_pairs().get_header() + "\tq值");
					bw.write(new value_pairs().get_header());
					bw.newLine();
					
					// System.out.println("最终结果总共需要输出：" + q.length + "行");
											
					// 判断我们预输出的文件是否存在
					if(merge.exists() && merge.isFile()){
						System.out.println("开始读取文件:" + merge);
							
						FileReader fh = new FileReader(merge);
						BufferedReader bh = new BufferedReader(fh);
						
						int i = 0;
						while ((content = bh.readLine()) != null){
								
							String[] contentarry = content.split("\\s+");
							bw.write(content);
							bw.newLine();
							/**	
							if(p.get(i).equals(Double.valueOf(contentarry[6]))){
								bw.write(content + "\t" + q[i]);
								i++;
								bw.newLine();
							}else{
								System.out.println("p值对应不上，终止输出于" + merge + "的" + i + "处");
								return;
							}
							*/
																					
						}
						bh.close();
						fh.close();
						
						/**
						if(i != p.size()){
							System.out.println("输出总数目与q值数目对不上");
						}
						*/
					}else{
						System.out.println(merge + "文件不存在");
						return;
				}
				bw.close();	
					
				// 最终将临时文件删除
				merge.delete();
				}else{
					///////////////////////////				单线程			///////////////////////////////////////////

					System.out.println("输出N:" + mirnalist.size() + "\n");
				
					// 这里新建table用来储存所有的cerna对的信息，包括NnKc等等所有数据
					results = merge(forc, rnalist, mirnalist.size());
					
					/**
					//	单线程计算结束		///////////////////////////////////////////////////
					System.out.println("开始统计q值");
					FDR fdr = new FDR();
					double[] q = fdr.padjust(get_p(results));
					*/
					
					// 输出最终结果
					File output = new File(args[1]);

					if(output.exists() && output.isFile()){
						System.out.println("使用已经存在的文件" + output);
					}else{
						try{
							output.createNewFile();
							System.out.println("创建新文件");
						}catch (IOException e){
							System.out.println("创建文件失败，错误信息：" + e.getMessage());
							return;
						}
					}
					
					// 创建BufferedWriter
					FileWriter fw = new FileWriter(output.getAbsoluteFile());
					BufferedWriter bw = new BufferedWriter(fw);
					// 写入表头，换行
					bw.write(results.get(0).get_header() + "\tq值");
					bw.newLine();
					// 使用循环把内容转换成字符串，统一输出，里边添加了一个判断，如果返回的结果为NA，那么略过不输出
					String message = null;
					System.out.println("总共需要输出：" + results.size() + "行");
					for(int i = 0; i < results.size(); i++){
						// message = results.get(i).get_pair() + "\t" +  q[i];
						message = results.get(i).get_pair();
						bw.write(message);
						bw.newLine();
					}
					
					bw.close();
				}
								
			} catch (FileNotFoundException e){ // 这部分是输入时候的异常，前边的代码嵌套在他里边，这里基本把异常沉默掉了，不输出
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			Time time = new Time();
			System.out.println("程序运行结束！");
			long end = System.currentTimeMillis();
			time.run_time(start, end);
		}
	}


	// 统计两个RNA共有的miRNA的个数
	private static int get_c(Map<String, Set<String>> forc, String rna1, String rna2){
		// 使用数值来操作相应的数组，这个就是初始值
		int c = 0;
		
		// 新建两个临时类，就为了从forc中获取相应的microRNA的Set
		Set<String> microrna_of_rna1 = forc.get(rna1);
		Set<String> microrna_of_rna2 = forc.get(rna2);
		
		
		// 计算两个rna占有的microrna总数，带重复
		int total_num_of_microrna = microrna_of_rna1.size() + microrna_of_rna2.size();
		
		// 创建新的set，将数组中的重复项去除
		Set single = new HashSet();
		
		/*
		 * 	如果i的数目不足第一个set的，就将第一个set的值赋给新建的set
		 * 	如果i的数目足够了，就开始将第二个set的值赋给新建的set
		 */
		
		for (int i = 0; i < total_num_of_microrna; i++){
			if(i < microrna_of_rna1.size()){
				single.add(microrna_of_rna1.toArray()[i]);
			}else{
				single.add(microrna_of_rna2.toArray()[i - microrna_of_rna1.size()]);
			}

		}

		c = (total_num_of_microrna - single.size());

		return(c);
	}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////	
	// 单线程的时候使用这个
	private static List<value_pairs> merge(Map<String, Set<String>>forc , Set rna, int N){

		int len = rna.size();
		
		/**
		 *  这里统计最终要计算多少对，通过递归方法即可
		 *  最终所有出错的根源应该就在这了
		 *  统计要计算多少对计算错了
		 */
		int size = digui(len - 1);
		System.out.println("总共要计算：" + size + "条");

		// 创建盛放结果的数组，改为List，省得后边还需要进行一个遍历去除NA值
		List<value_pairs> results = new ArrayList<value_pairs>();
		
		// 两个嵌套循环统计各种值，最后放入一个value_pairs自定义类的数组中
		int round = 0;
		int jindu = 0;
		 
		for(int i = 0; i < len; i++){
			
			for(int j = i + 1; j < len; j++){

				int c = get_c(forc, rna.toArray()[i].toString(), rna.toArray()[j].toString());
				
				/*
				 *  如果两个rna没有共同的microrna那就输出剩下的了
				 *  如果有，那么就获取相应的K，n值赋值计算
				 */
				
				if( c > 0){
					results.add(new value_pairs( rna.toArray()[i].toString(), 
							rna.toArray()[j].toString(), N, 
							forc.get(rna.toArray()[i].toString()).size(),
							forc.get(rna.toArray()[j].toString()).size(), c));
				}
				
				round++;
				
				// System.out.println("round" + round);
				// 用来输出ceRNA对的计算进度
				if(jindu != (round * 100)/size){
					jindu = (round * 100)/size;
					System.out.println("ceRNA对计算进度：" + jindu + "%");
				}
			}
		}
		
		return results;

	}
	

	// 多线程使用这个
	// 需要注意的是多线程，我通过缩小输入的内容来实现，此时，计算的对数就会变小很多很多，需要把这个拉起来
	private static List<value_pairs> merge(List rnalist, List rnashort, Map<String, Set<String>> forc, int N, String sig, boolean same){

		int lenshort = rnashort.size();
		
		int lenall = rnalist.size();
				
		/**
		 *  这里统计最终要计算多少对，通过递归方法即可
		 *  最终所有出错的根源应该就在这了
		 *  统计要计算多少对计算错了
		 */
		

		// 创建盛放结果的数组
		List<value_pairs> results = new ArrayList<value_pairs>();
		// 两个嵌套循环统计各种值，最后放入一个value_pairs自定义类的数组中
		int round = 0;
		int jindu = 0;
		
				
		if(same){
			
			int size = digui(lenall-1);
			System.out.println(sig + " - 总共要计算：" + size + "条");
			
			for(int i = 0; i < lenshort; i++){
				
				for(int j = i+1; j < lenall; j++){

					int c = get_c(forc, rnashort.get(i).toString(), rnalist.get(j).toString());
					
					if( c > 0){
						results.add(new value_pairs(rnashort.get(i).toString(), 
								rnalist.get(j).toString(), N, 
								forc.get(rnashort.get(i)).size(),
								forc.get(rnalist.get(j)).size(), c));
					}					
					round++;
					
					// 用来输出ceRNA对的计算进度
					if(jindu != (round * 100)/size){
						jindu = (round * 100)/size;
						System.out.println(sig + " - ceRNA对计算进度：" + jindu + "%");
					}
				}
			}
		}else{
			
			int size = lenshort * lenall;
			System.out.println(sig + " - 总共要计算：" + size + "条");
			
			for(int i = 0; i < lenshort; i++){
				
				for(int j = 0; j < lenall; j++){
					// System.out.println("i : " + i + "\t" + "j : " + j);
					int c = get_c(forc, rnashort.get(i).toString(), rnalist.get(j).toString());
					
					if( c > 0){
						results.add(new value_pairs(rnashort.get(i).toString(), 
								rnalist.get(j).toString(), N, 
								forc.get(rnashort.get(i)).size(),
								forc.get(rnalist.get(j)).size(), c));
					}
					
					round++;
					
					// System.out.println("round" + round);
					// 用来输出ceRNA对的计算进度
					if(jindu != (round * 100)/size){
						jindu = (round * 100)/size;
						System.out.println(sig + " - ceRNA对计算进度：" + jindu + "%");
					}
				}
			}
		}

		
		return results;

	}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////	
	
	// 递归计算数列之和
	private static int digui(int n){
		if(n > 0) {
			return n + digui(n-1);
		}else{
			return 0;
		}
	}
	
	// 从自定义数组中提取出所有的p值，返回一个double型数组
	private static double[] get_p(List<value_pairs> table){
		double[] p = new double[table.size()];	// 新建用于储存结果的double数组
		//	其实这里按理说不应当存在NullPointerException的异常了，但是留着也无妨懒得删了
		try{
			for ( int i = 0; i < table.size(); i++){
				p[i] = table.get(i).get_p();
			}
		}catch(NullPointerException e){
			System.out.println("从所有的value_pairs中抽取p值时出错");
		}

		return p;
	}

		
	// 多线程的接口  ///////////////////////////////////////////////////////////////////////////////////////////////

	private static class Multi implements Runnable{
		private int total_mirna;	
		private String num_of_Thread;
		private List rnalist;
		private List rnashort;
		private Map<String, Set<String>> forc;
		private boolean same = true;
		private String output;
		
		public void setMulti(List rnalist,List rnashort , Map<String, Set<String>> forc, int N,  String output,String num_of_Thread){
			this.total_mirna = N;
			this.num_of_Thread = num_of_Thread;
			this.rnalist = rnalist;
			this.rnashort = rnashort;
			this.forc = forc;
			this.same = false;
			this.output = output;
		}
		
		public void setMulti(List rnalist, Map<String, Set<String>> forc, int N, String output,String num_of_Thread){
			this.total_mirna = N;
			this.num_of_Thread = num_of_Thread;
			this.forc = forc;
			this.rnalist = rnalist;
			this.rnashort = rnalist;
			this.output = output;
		}
		
		public void run(){
			
			System.out.println("线程" + num_of_Thread + ": 输出N:" + this.total_mirna + "\n");
			
		
			// 这里新建table用来储存所有的cerna对的信息，包括NnKc等等所有数据
			List<value_pairs> table = merge(this.rnalist, this.rnashort, this.forc, 
					this.total_mirna, "线程" + this.num_of_Thread, this.same);
			/**
			 *  去除merge计算后包含的NA值
			 *  先使用一个循环遍历确定有多少NA
			 *  然后新建一个新的value_pairs数组，大小刚好为原数组去除NA之后的数目
			 *  然后第二个循环，将所有不为NA的值提取出来
			 * @param table
			 * @return
			 */			
			System.out.println("线程" + this.num_of_Thread + ": ceRNA表单已经计算已完成");
			
			/*
			for(int i = 0; i < results.length; i++){
				System.out.println(i + "\t" + results[i].get_c());
			}
			*/
		
			
			// 设置输出文件，判断是否存在，不存在就创建
			File output = new File(this.output);

			if(output.exists() && output.isFile()){
				System.out.println("使用已经存在的文件" + output);
			}else{
				try{
					output.createNewFile();
					System.out.println("创建新文件");
				}catch (IOException e){
					System.out.println("创建文件失败，错误信息：" + e.getMessage());
					return;
				}
			}

			// 使用BufferedWriter写入文件，比PrintWriter的优点就是不用转换成bytes格式，但是bytes不存在编码的问题吧，好像
			try{
				// 创建BufferedWriter
				FileWriter fw = new FileWriter(output.getAbsoluteFile());
				BufferedWriter bw = new BufferedWriter(fw);
				// 使用循环把内容转换成字符串，统一输出，里边添加了一个判断，如果返回的结果为NA，那么略过不输出
				String message = null;
				System.out.println("线程" + this.num_of_Thread + ": 总共需要输出：" + table.size() + "行");
				for(int i = 0; i < table.size(); i++){
					message = table.get(i).get_pair();
					bw.write(message);
					bw.newLine();
				}
				bw.close();
			}catch (FileNotFoundException e){ // 这部分是输入时候的异常，前边的代码嵌套在他里边，这里基本把异常沉默掉了，不输出
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
	}
}

